﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using ESRI.ArcGIS.ADF.Serialization;
using ESRI.ArcGIS.ArcMapUI;
using ESRI.ArcGIS.DataSourcesRaster;
using ESRI.ArcGIS.Desktop.AddIns;
using ESRI.ArcGIS.Framework;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.esriSystem;

namespace Bes.Swsp.ToolBox
{
  public class ToolBoxExtension : Extension
  {
    private static ToolBoxExtension _extension;
    private ToolBoxSettings _settings;

    public ToolBoxExtension()
    {
      Logger.StartLogging();
    }

    /// <summary>
    /// A reference to the instantiated ToolBox Extension.
    /// </summary>
    internal static ToolBoxExtension Extension
    {
      get { return _extension; }
    }

    /// <summary>
    /// Toolbox setup information.
    /// </summary>
    internal ToolBoxSettings Settings
    {
      get
      {
        if (_settings == null)
        {
          _settings = new ToolBoxSettings();
          _settings.PropertyChanged += _Settings_PropertyChanged;
        }
        return _settings;
      }
    }

    protected override void OnStartup()
    {
      Logger.StartLogging();

      ArcMap.Events.NewDocument += _DocumentEvents_NewDocument;
      ArcMap.Events.OpenDocument += _DocumentEvents_OpenDocument;
      ArcMap.Events.CloseDocument += _DocumentEvents_CloseDocument;

      _extension = this;
    }

    protected override void OnShutdown()
    {
      if (_settings != null)
      {
        _settings.Dispose();
        _settings = null;
      }

      ArcMap.Events.NewDocument -= _DocumentEvents_NewDocument;
      ArcMap.Events.OpenDocument -= _DocumentEvents_OpenDocument;
      ArcMap.Events.CloseDocument -= _DocumentEvents_CloseDocument;

      Logger.StopLogging();
    }

    protected override void OnLoad(Stream inStrm)
    {
      ToolBoxSettings settings = null;
      try
      {
        // NOTE: Do not close or dispose BinaryReader, as this will close the Stream
        BinaryReader reader = new BinaryReader(inStrm);
        int version = reader.ReadInt32();

        if (version >= 1)
        {
          settings = new ToolBoxSettings();

          // Version 1:
          // Item 1: pipe geometric network (as FeatureClassName for orphan junction class)
          // Item 2: inlet feature class (as FeatureClassName)
          // Item 3: catchment feature class (as FeatureClassName)
          // Item 4: flow direction dataset (as RasterDatasetName)
          // Item 5: flow accumulation dataset (as RasterDatasetName)
          // Item 6: storm nodes feature class (as FeatureClassName)

          FeatureClassName orphanName = null;
          PersistenceHelper.Load(inStrm, ref orphanName);
          INetworkClass orphanClass = SafeOpen(orphanName) as INetworkClass;
          if (orphanClass != null)
            settings.PipeNetwork = orphanClass.GeometricNetwork;

          // As of version 4: NO LONGER USED (null placeholder used instead)
          FeatureClassName inletName = null;
          PersistenceHelper.Load<FeatureClassName>(inStrm, ref inletName);

          FeatureClassName catchName = null;
          PersistenceHelper.Load<FeatureClassName>(inStrm, ref catchName);
          settings.CatchmentFeatureClass = catchName == null ? null : SafeOpen(catchName) as IFeatureClass;

          RasterDatasetName flowDirName = null;
          PersistenceHelper.Load<RasterDatasetName>(inStrm, ref flowDirName);
          IRasterDataset flowDirDataset = SafeOpen(flowDirName) as IRasterDataset;
          settings.FlowDirectionRaster = flowDirDataset == null ? null : flowDirDataset.CreateDefaultRaster();

          RasterDatasetName flowAccName = null;
          PersistenceHelper.Load<RasterDatasetName>(inStrm, ref flowAccName);
          IRasterDataset flowAccDataset = SafeOpen(flowAccName) as IRasterDataset;
          settings.RunoffRaster = flowAccDataset == null ? null : flowAccDataset.CreateDefaultRaster();

          FeatureClassName nodesName = null;
          PersistenceHelper.Load<FeatureClassName>(inStrm, ref nodesName);
          settings.StormNodesFeatureClass = nodesName == null ? null : SafeOpen(nodesName) as IFeatureClass;
        }

        if (version >= 2)
        {
          // Version 2:
          // Item 7: conveyance feature class (as FeatureClassName)

          FeatureClassName conveyanceName = null;
          PersistenceHelper.Load<FeatureClassName>(inStrm, ref conveyanceName);
          settings.StormConveyanceFeatureClass = conveyanceName == null ? null : SafeOpen(conveyanceName) as IFeatureClass;
        }

        if (version >= 3)
        {
          // Version 3:
          // Item 8: Flow accumulation threshold
          // Item 9: Include terminal flow
          // Item 10: local consequence accumulation raster (as RasterDatasetName)

          settings.FlowAccumulationThreshold = reader.ReadDouble();

          settings.IncludeTerminalFlow = reader.ReadBoolean();

          RasterDatasetName accumulationName = null;
          PersistenceHelper.Load<RasterDatasetName>(inStrm, ref accumulationName);
          IRasterDataset accumulationDataset = SafeOpen(accumulationName) as IRasterDataset;
          settings.AccumulationRaster = accumulationDataset == null ? null : accumulationDataset.CreateDefaultRaster();
        }

        if (_settings != null)
        {
          _settings.Dispose();
        }
        _settings = settings;
      }
      catch (Exception ex)
      {
        Logger.Log("ToolBoxExtension.OnLoad: " + ex.GetType().FullName + ": " + ex.Message);
        _settings = null;
      }
    }

    protected override void OnSave(Stream outStrm)
    {
      if (_settings != null)
      {
        // NOTE: Do not close or dispose BinaryWriter, as this will close the Stream
        BinaryWriter writer = new BinaryWriter(outStrm);
        int version = 4;
        writer.Write(version);

        // ************** VERSION 1 **************

        // Item 1: pipe geometric network (as FeatureClassName for orphan junction class)
        FeatureClassName orphanName = _settings.PipeNetwork == null ? null : ((IDataset)_settings.PipeNetwork.OrphanJunctionFeatureClass).FullName as FeatureClassName;
        PersistenceHelper.Save<FeatureClassName>(outStrm, orphanName);

        // Item 2: NO LONGER USED (used to be inlet feature class)
        PersistenceHelper.Save<FeatureClassName>(outStrm, null);

        // Item 3: catchment feature class (as FeatureClassName)
        FeatureClassName catchName = _settings.CatchmentFeatureClass == null ? null : ((IDataset)_settings.CatchmentFeatureClass).FullName as FeatureClassName;
        PersistenceHelper.Save<FeatureClassName>(outStrm, catchName);

        // Item 4: flow direction dataset (as RasterDatasetName)
        IDataset flowDirDataset = _settings.FlowDirectionRaster == null ? null : ((IRasterAnalysisProps)_settings.FlowDirectionRaster).RasterDataset as IDataset;
        RasterDatasetName flowDirName = flowDirDataset == null ? null : flowDirDataset.FullName as RasterDatasetName;
        PersistenceHelper.Save<RasterDatasetName>(outStrm, flowDirName);

        // Item 5: flow accumulation dataset (as RasterDatasetName)
        IDataset flowAccDataset = _settings.RunoffRaster == null ? null : ((IRasterAnalysisProps)_settings.RunoffRaster).RasterDataset as IDataset;
        RasterDatasetName flowAccName = flowAccDataset == null ? null : flowAccDataset.FullName as RasterDatasetName;
        PersistenceHelper.Save<RasterDatasetName>(outStrm, flowAccName);

        // Item 6: storm nodes feature class (as FeatureClassName)
        FeatureClassName nodesName = _settings.StormNodesFeatureClass == null ? null : ((IDataset)_settings.StormNodesFeatureClass).FullName as FeatureClassName;
        PersistenceHelper.Save<FeatureClassName>(outStrm, nodesName);

        // ************** VERSION 2 **************

        // Item 7: storm conveyance feature class (as FeatureClassName)
        FeatureClassName conveyanceName = _settings.StormConveyanceFeatureClass == null ? null : ((IDataset)_settings.StormConveyanceFeatureClass).FullName as FeatureClassName;
        PersistenceHelper.Save<FeatureClassName>(outStrm, conveyanceName);

        // ************** VERSION 3 **************

        // Item 8: Flow accumulation threshold
        writer.Write(_settings.FlowAccumulationThreshold);

        // Item 9: Include terminal flow
        writer.Write(_settings.IncludeTerminalFlow);

        // Item 10: local consequence accumulation raster (as RasterDatasetName)
        IDataset accumulationRasterDataset = _settings.AccumulationRaster == null ? null : ((IRasterAnalysisProps)_settings.AccumulationRaster).RasterDataset as IDataset;
        RasterDatasetName accumulationRasterName = accumulationRasterDataset == null ? null : accumulationRasterDataset.FullName as RasterDatasetName;
        PersistenceHelper.Save<RasterDatasetName>(outStrm, accumulationRasterName);

        // ************** VERSION 4 **************

        // No new items. Instead Item 2 is now removed (but a null placeholder is saved in its place--see above).
      }
    }

    internal static object SafeOpen(IName datasetName)
    {
      // Returns null if opening dataset fails (e.g. dataset no longer exists)
      object result = null;
      if (datasetName != null)
      {
        try
        {
          result = datasetName.Open();
        }
        catch (COMException ex)
        {
          // Do nothing
          Logger.Log(string.Format("ToolBoxExtension._SafeOpen: {0}: {1} ({2})", ex.GetType().FullName, ex.Message, ex.ErrorCode));
        }
      }
      return result;
    }

    private void _DocumentEvents_CloseDocument()
    {
      _ClearProject();
    }

    private void _DocumentEvents_OpenDocument()
    {
      // Do not clear when opening a document, since it may have a saved setup
    }

    private void _DocumentEvents_NewDocument()
    {
      _ClearProject();
    }

    private void _ClearProject()
    {
      // A null setup object indicates this document has not been used for the SWSP toolbox
      _settings = null;
    }

    private void _Settings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
      ((IDocumentDirty2)ArcMap.Document).SetDirty();
    }
  }
}
